package TDALista;
import java.util.Iterator;

/**
 * Clase NodePositionList generica, implementacion de una lista doblemente enlazada mediante nodos.
 * @author Jonathan Fritz - Santiago Salem - Kevin Schweitzer.
 * @param E Tipo de los datos de la lista.
 */
public class NodePositionList<E> implements PositionList<E> {
 
	protected int numElts;
	protected DNode<E> header,trailer;
	
	/** Constructor crea una nueva lista vacia*/
	  public NodePositionList() {
	    numElts = 0;
	    header = new DNode<E>(null, null, null);	
	    trailer = new DNode<E>(header, null, null);	
	    header.setNext(trailer);	
	  }
	  /** Chequea si la posicion pasada por parametro es correcta.
	   * para esta lista y la convierte en Nodo.
	   * @param p Posicion a chequear.
	   * @throws InvalidPositionException si la posicion no es valida.
	   * @return Posicion convertida en nodo.
	   */
	  protected DNode<E> checkPosition(Position<E> p)
	    throws InvalidPositionException {
	    if (p == null)
	      throw new InvalidPositionException
		("Se ha pasado una posicion nula");
	    if (p == header)
		throw new InvalidPositionException
		  ("header no es una posicion valida");
	    if (p == trailer)
		throw new InvalidPositionException
		  ("trailer no es una posicion valida");
	    try {
	      DNode<E> temp = (DNode<E>) p;
	      if ((temp.getPrev() == null) || (temp.getNext() == null))
		throw new InvalidPositionException
		  ("La posicion no pertenece a una lista valida");
	      return temp;
	    } catch (ClassCastException e) {
	      throw new InvalidPositionException
		("La posicion es de un tipo incorrecto para esta lista");
	    }
	  }
	  /** Retorna la cantidad de elementos de la lista.
		  *@return cantidad de elementos de la lista.
		  */
	  public int size() { return numElts; }
	  
	  /** Retorna verdadero en caso que la lista este vacia, falso en caso contrario. 
	   * @return verdadero si la lista esta vacia, sino falso.
	   */
	  public boolean isEmpty() { return (numElts == 0); }
	  
	  /** Retorna la primera posicion de la lista.
	   * @return primera posicion de la lista.
	   * @throws EmptyListException si la lista esta vacia.
	   */
	  public Position<E> first() throws EmptyListException {
	    if (isEmpty())
	      throw new EmptyListException("La lista esta vacia");
	    return header.getNext();
	  }
	  
	  /** Retorna una posicion anterior a la dada.
	   * @return posicion previa a la dada.
	   * @param p Posicion de la cual se devuelve la posicion previa.
	   *@throws BoundaryViolationException si se pide el anterior al primer elemento. 
	   */
	  public Position<E> prev(Position<E> p)
	      throws InvalidPositionException, BoundaryViolationException {
	    DNode<E> v = checkPosition(p);
	    DNode<E> prev = v.getPrev();
	    if (prev == header)
	      throw new BoundaryViolationException
		("No se puede retornar el elemento anterior al primero");
	    return prev;
	  }
	  
	  /** Inserta un elemento delante de la posicion dada.
	    * @param p Posicion delante de la cual se debe insertar.
	    * @param element Elemento a insertar.
	    */
	  public void addBefore(Position<E> p, E element) 
	      throws InvalidPositionException {
	    DNode<E> v = checkPosition(p);
	    numElts++;
	    DNode<E> newNode = new DNode<E>(v.getPrev(), v, element);
	    v.getPrev().setNext(newNode);
	    v.setPrev(newNode);
	  }
	  
	  /** Inserta un elemento al principio de la lista.
	    *@param element Elemento a insertar.
	    */
	  public void addFirst(E element) {
	    numElts++;
	    DNode<E> newNode = new DNode<E>(header, header.getNext(), element);
	    header.getNext().setPrev(newNode);
	    header.setNext(newNode);
	  }
	  
	  /**Remueve el elemento de la posicion dada.
	   * @param p Posicion de la cual se debe eliminar.
	   * @return Elemento de la posicion eliminada. 
	   */
	  public E remove(Position<E> p)
	      throws InvalidPositionException {
	    DNode<E> v = checkPosition(p);
	    numElts--;
	    DNode<E> vPrev = v.getPrev();
	    DNode<E> vNext = v.getNext();
	    vPrev.setNext(vNext);
	    vNext.setPrev(vPrev);
	    E vElem = v.element();
	 // Desliga la posicion de la lista y la hace invalida
	    v.setNext(null);
	    v.setPrev(null);
	    return vElem;
	  }
	  
	  /** Reemplaza el elemento de la posicion dada por otro.
	    * y retorna el elemento anterior.
	    *@param p Posicion en la cual insertar y de la cual se retorna el anterior.
	    *@param e Nuevo elemento a insertar.
	    *@return Elemento reemplazado.
	    */
	  public E set(Position<E> p, E element)
	      throws InvalidPositionException {
	    DNode<E> v = checkPosition(p);
	    E oldElt = v.element();
	    v.setElement(element);
	    return oldElt;
	  }
	  
	  /** Inserta un elemento al final de la lista */
	  public void addLast(E e){
		  DNode<E> newNode =new DNode<E>(trailer.getPrev(),trailer,e);
		  numElts++;
		  trailer.getPrev().setNext(newNode);
		  trailer.setPrev(newNode);
		  
	  }
	  
	  /** Retorna el ultimo elemento de la lista.
	   *@throws EmptyListException si la lista esta vacia. 
	   *@return ultimo elemento de la lista.
	   */
	  public Position<E> last() throws EmptyListException{
		  if(isEmpty()) throw new EmptyListException("La lista esta vacia");
		  return trailer.getPrev();
	  }
	  
	  /** Retorna el nodo siguiente al dado.
	   *@param p Posicion de la cual se tiene que retornar el siguiente nodo.
	   *@throws BoundaryViolationException si se pide el elemento siguiente al ultimo.
	   *@return Nodo siguiente al dado.
	   */
	  public Position<E> next(Position<E> p) throws InvalidPositionException, BoundaryViolationException{
		  DNode<E> v=checkPosition(p);
		  DNode<E> next=v.getNext();
		  if(next==trailer) throw new BoundaryViolationException("No se puede retornar la posicion siguiente a la ultima");
		  
		  return next;
	  }
	  
	  /** Inserta un elemento despues de la posicion dada.
	   *@param p Posicion despues de la cual se debe insertar.
	   *@param e Elemento a insertar.
	   */
	  public void addAfter(Position<E> p, E e) throws InvalidPositionException{
		  DNode<E> v = checkPosition(p);
		    numElts++;
		    DNode<E> newNode = new DNode<E>(v, v.getNext(), e);
		    v.getNext().setPrev(newNode);
		    v.setNext(newNode);
	  }
	  
	  public Iterator<E> iterator(){
		  return new ElementIterator<E>(this);
	  }
	  
	  public Iterable<Position<E>> positions(){
		  PositionList<Position<E>> lista = new NodePositionList<Position<E>>();
		  if(numElts != 0){
			  Position<E> p = header.getNext();
			  while(p!=null){
				  lista.addLast(p);
				  try{
					  if (p!=last())
						  p = next(p);
					  else
						  p = null;
				  }
				  catch(EmptyListException | InvalidPositionException | BoundaryViolationException e){
					  System.out.println(e.getMessage());
				  }
			  }
		  }
		  return lista;
	  }
}
